package com.zbs.framework.security.utils;

import com.alibaba.fastjson.JSONObject;
import com.zbs.framework.common.constant.Constants;
import com.zbs.framework.common.redis.RedisUtil;
import com.zbs.framework.common.utils.CommonUtil;
import com.zbs.framework.common.utils.ErrEnum;
import com.zbs.framework.common.utils.ErrorAssert;
import com.zbs.framework.common.utils.JwtUtils;
import com.zbs.framework.security.config.FrameworkConstants;
import com.zbs.framework.security.entity.Claim;
import com.zbs.framework.security.entity.LoginUserInfo;
import io.jsonwebtoken.JwtBuilder;
import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.Map;
import java.util.Set;

/**
 * @author: Zbs
 * @createTime: 2021/2/25 10:56
 * token处理
 */
@Component
@DependsOn({"frameworkConstants", "redisUtil"})
public class TokenManager {

    private static final String usernameKey = "username";
    private static final String idKey = "id";
    private static final String entryTypeKey = "entryType";

    public static String createToken(String id, String username, String entryType) {
        return JwtUtils.createJwtToken(FrameworkConstants.TOKEN_EXPIRATION, FrameworkConstants.TOKEN_SIGN_KEY).setSubject(username)
                .claim(usernameKey, username)
                .claim(idKey, id)
                .claim(entryTypeKey, entryType).compact();
    }

    public static String createToken(String subject, Claim claim) {
        Map<String, Object> map = claim.getMap();
        JwtBuilder jwtBuilder = JwtUtils.createJwtToken(FrameworkConstants.TOKEN_EXPIRATION, FrameworkConstants.TOKEN_SIGN_KEY).setSubject(subject);
        if (!map.isEmpty()) {
            map.keySet().forEach(key -> jwtBuilder.claim(key, map.get(key)));
        }
        return jwtBuilder
                .compact();
    }

    public static LoginUserInfo getLoginUserNotCheck(String token) {
        String username = getUserNameNotCheck(token);
        String entryType = getTokenValueNotCheck(token, entryTypeKey);
        final String userInfo;
        if (FrameworkConstants.IS_SSO && FrameworkConstants.IS_MULTI_TERMINAL) {
            userInfo = RedisUtil.get(Constants.tokenPrefix + CommonUtil.MD5_32(username) + Constants.tokenSuffix + entryType);
        } else {
            userInfo = RedisUtil.get(Constants.tokenPrefix + CommonUtil.MD5_32(username) + Constants.tokenSuffix + "1");
        }
        if (StringUtils.hasLength(userInfo)) {
            return JSONObject.parseObject(userInfo, LoginUserInfo.class);
        }
        return null;
    }

    public static LoginUserInfo getLoginUser(String token) {
        //从redis里获取用户登录信息
        final String userInfo;
        final String username = TokenManager.getUserName(token);
        final String entryType = TokenManager.getEntryType(token);
        if (FrameworkConstants.IS_SSO && FrameworkConstants.IS_MULTI_TERMINAL) {
            userInfo = RedisUtil.get(Constants.tokenPrefix + CommonUtil.MD5_32(username) + Constants.tokenSuffix + entryType);
        } else {
            userInfo = RedisUtil.get(Constants.tokenPrefix + CommonUtil.MD5_32(username) + Constants.tokenSuffix + "1");
        }

        if (StringUtils.hasLength(userInfo)) {
            LoginUserInfo securityUser = JSONObject.parseObject(userInfo, LoginUserInfo.class);
            ErrorAssert.isTrue(securityUser.isEnabled(), ErrEnum.USER_IS_ENABLE);
            if (FrameworkConstants.IS_SSO) {
                ErrorAssert.isTrue(token.equals(securityUser.getToken()), ErrEnum.ELSEWHERE_IN_LOGIN);
            }
            return securityUser;
        }
        return null;
    }

    public static LoginUserInfo getLoginUserNotException(String token) {
        //从redis里获取用户登录信息
        final LoginUserInfo userInfo;
        final String username = TokenManager.getUserNameNotCheck(token);
        final String entryType = TokenManager.getEntryTypeNotCheck(token);
        if (FrameworkConstants.IS_SSO && FrameworkConstants.IS_MULTI_TERMINAL) {
            userInfo = RedisUtil.getObject((Constants.tokenPrefix + CommonUtil.MD5_32(username) + Constants.tokenSuffix + entryType),LoginUserInfo.class);
        } else {
            userInfo = RedisUtil.getObject((Constants.tokenPrefix + CommonUtil.MD5_32(username) + Constants.tokenSuffix + "1"),LoginUserInfo.class);
        }
        return userInfo;
    }


    public static void checkLoginUser(String token) {
        ErrorAssert.notNull(getLoginUser(token), ErrEnum.TOKEN_BE_OVERDUE);
    }

    public static String getId(String token) {
        return getTokenValue(token, idKey);
    }

    public static String getUserName(String token) {
        ErrorAssert.isTrue(checkToken(token), ErrEnum.TOKEN_BE_OVERDUE);
        return getUserNameNotCheck(token);
    }

    public static String getUserNameNotCheck(String token) {
        return JwtUtils.getJwtClaims(token, FrameworkConstants.TOKEN_SIGN_KEY).getSubject();
    }

    public static String getEntryType(String token) {
        return getTokenValue(token, entryTypeKey);
    }

    public static String getEntryTypeNotCheck(String token) {
        return getTokenValueNotCheck(token, entryTypeKey);
    }


    public static String getTokenValue(String token, String key) {
        ErrorAssert.isTrue(checkToken(token), ErrEnum.TOKEN_BE_OVERDUE);
        return getTokenValueNotCheck(token, key);
    }

    public static String getTokenValueNotCheck(String token, String key) {
        return (String) JwtUtils.getJwtClaims(token, FrameworkConstants.TOKEN_SIGN_KEY).get(key);
    }

    /**
     * 判断token是否存在与有效
     *
     * @param token
     * @return
     */
    public static boolean checkToken(String token) {
        return JwtUtils.checkToken(token, FrameworkConstants.TOKEN_SIGN_KEY) && getLoginUserNotException(token) != null;
    }

    public static void removeToken(String username, String entryType) {
        RedisUtil.del(Constants.tokenPrefix + CommonUtil.MD5_32(username) + Constants.tokenSuffix + entryType);
    }

    public static void clearAllToken(String username) {
        Set<String> keysByLikeKey = RedisUtil.findKeysByLikeKey(Constants.tokenPrefix + CommonUtil.MD5_32(username) + Constants.tokenSuffix + "?");
        keysByLikeKey.forEach(RedisUtil::del);
    }


    public static void isEnabled(String username, int isEnabled) {
        Set<String> keysByLikeKey = RedisUtil.findKeysByLikeKey(Constants.tokenPrefix + CommonUtil.MD5_32(username) + Constants.tokenSuffix + "?");
        if (!CollectionUtils.isEmpty(keysByLikeKey)) {
            keysByLikeKey.forEach(key -> {
                final String exists = RedisUtil.get(key);
                if (exists != null) {
                    final long expire = RedisUtil.getExpire(key);
                    LoginUserInfo loginUserInfo = JSONObject.parseObject(exists, LoginUserInfo.class);
                    loginUserInfo.setEnabled(isEnabled == 1);
                    RedisUtil.set(CommonUtil.MD5_32(key), JSONObject.toJSONString(loginUserInfo), expire);
                }
            });
        }
    }
}
